package mac10.bean;

import java.io.IOException;

import org.w3c.dom.Document;

import de.dailab.jiactng.agentcore.IAgentBean;
import de.dailab.jiactng.agentcore.action.AbstractMethodExposingBean;
import mac10.connection.ActionFactory;
import mac10.connection.ServerConnection;
import mac10.ontology.Authentication;

/**
 * Manages the connection to the Agent Contest simulation server.
 * 
 * @author axle
 */
public class ServerCommunicationBean extends AbstractMethodExposingBean {
	private String host = "localhost";
	private int port = 12300;
	private String username = "username";
	private String password = "passwort";

	/** The simulation server connection. */
	private ServerConnection csConnection = null;

	/** The thread listening to simulation server messages. */
	private ACControlThread ccThread;

	private AgentBean perceptionBean;

	private boolean firstConnect = true;
	private static final int FIRST_CONNECT_INTERVAL = 10000;
	private static final int CONNECT_INTERVAL = 100;

	/**
	 * Returns the name of the host where the simulation server is running.
	 * 
	 * @return the name of the host
	 */
	public String getHost() {
		return host;
	}

	/**
	 * Sets the name of the host where the simulation server is running.
	 * 
	 * @param host
	 *            the name of the host
	 */
	public void setHost(String host) {
		this.host = host;
	}

	/**
	 * Returns the password the agent uses during authorization phase.
	 * 
	 * @return the password
	 */
	public String getPassword() {
		return password;
	}

	/**
	 * Sets the password the agent uses during authorization phase.
	 * 
	 * @param password the password
	 */
	public void setPassword(String password) {
		this.password = password;
	}

	/**
	 * Return the port number the simulation server is listening to.
	 * 
	 * @return the port number
	 */
	public int getPort() {
		return port;
	}

	/**
	 * Sets the port number the simulation server is listening to.
	 * 
	 * @param port the port number
	 */
	public void setPort(int port) {
		this.port = port;
	}

	/**
	 * Returns the name the agent uses during authorization phase.
	 * 
	 * @return the name
	 */
	public String getUsername() {
		return username;
	}

	/**
	 * Sets the name the agent uses during authorization phase.
	 * 
	 * @param username the name
	 */
	public void setUsername(String username) {
		this.username = username;
	}

	/**
	 * Memorizes authentication data and initializes the server connection.
	 */
	@Override
	public void doInit() throws Exception {
		super.doInit();
		for (IAgentBean ab:thisAgent.getAgentBeans()) {
			if (ab instanceof AgentBean) {
				this.perceptionBean = (AgentBean)ab;
			}
		}
		memory.write(new Authentication(username, password));
		log.warn(new StringBuffer("Username=").append(username));
		this.perceptionBean.setUsername(username);
	}
	
	

	@Override
	public void doStart() throws Exception {
		super.doStart();
		if (connect()) {
			firstConnect = false;
			setExecuteInterval(0);
		} else {
			setExecuteInterval(FIRST_CONNECT_INTERVAL);
		}
	}

	@Override
	public void doStop() throws Exception {
		closeUnderlyingConnection();
		super.doStop();
	}

	@Override
	public void execute() {
		if (connect()) {
			firstConnect = false;
			setExecuteInterval(0);
		} else {
			setExecuteInterval(firstConnect ? FIRST_CONNECT_INTERVAL
					: CONNECT_INTERVAL);
		}
	}

	private synchronized boolean connect() {
		if (csConnection != null) {
			log.debug("Connection is still alive?");
		} else {
			log.debug("connecting...");
		}

		try {
			csConnection = new ServerConnection(host, port, log);
			sendActionResponse(ActionFactory.getAuthenticationAction(username, password));
			// start listening to simulation server
			ccThread = new ACControlThread();
			ccThread.start();
			return true;
		} catch (IOException e) {
			log.error("connection failed!");
			closeUnderlyingConnection();
		}

		return false;
	}

	private void closeUnderlyingConnection() {
		csConnection = null;
		// avoid deadlock if this method was called out of the receiver thread
		if (ccThread != null && ccThread != Thread.currentThread()) {
			try {
				ccThread.join();
			} catch (InterruptedException ie) {
				log.warn("could not join with reader thread");
			}
			ccThread = null;
		}
	}

	@Expose(name = "sendActionResponse")
	public void sendActionResponse(Document doc) {
		try {
			csConnection.sendDocument(doc);
		} catch (Exception e) {
			log.error(e);
		}
	}

	/**
	 * Thread that listens to the simulation server. Contains the simulation
	 * loop.
	 */
	public class ACControlThread extends Thread {

		/**
		 * The main control loop: receive documents in loop and parse them
		 * 
		 * Note: The first received message should be the autentication
		 * response.
		 * 
		 */
		public void run() {
			log.warn("Alive...");

			while (!isInterrupted() && csConnection != null) {
				try {
					Document message = csConnection.receiveDocument();
					if (perceptionBean != null) {
						perceptionBean.processServerMessage(message);
					}
				} catch (Exception e) {
					e.printStackTrace();
					log.warn("connection closed -> open again!");
					closeUnderlyingConnection();
					setExecuteInterval(CONNECT_INTERVAL);
				}
			}
		}
	}
}
